/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019.
 * Description: dts reg operation language support
 * Author: yejianhua
 * Create: 2019-09-20
 */
#include <linux/hal/drol.h>
#include <linux/of.h>
#include <linux/of_device.h>

static phys_addr_t recalculate_addr(struct device_node *child, phys_addr_t base,
		u32 irq)
{
	u32 width;
	phys_addr_t cal;

	if (of_property_read_u32(child, "width", &width))
		width = 0;

	cal = base + (phys_addr_t)irq * width;
	if (cal < base) {
		pr_err("calculate result overflow\n");
		return base;
	}

	return cal;
}

static bool value_in_node(u32 irq, u32 val, struct device_node *np)
{
	u32 source_max;
	u32 source_min;
	u32 irq_min;
	u32 irq_max;
	bool found = false;
	int invalid = 0;

	if (of_property_read_u32(np, "source_max", &source_max)) {
		invalid++;
		source_max = 0;
	}

	if (of_property_read_u32(np, "source_min", &source_min)) {
		invalid++;
		source_min = 0;
	}

	/* 2 means that the current node is invalid */
	if (invalid == 2)
		return false;

	if (val >= source_min && val <= source_max)
		found = true;

	if (!of_property_read_u32(np, "irq_min", &irq_min)) {
		if (irq < irq_min)
			found = false;
	}

	if (!of_property_read_u32(np, "irq_max", &irq_max)) {
		if (irq > irq_max)
			found = false;
	}

	if ((!of_property_read_u32(np, "non_irq_min", &irq_min)) &&
		(!of_property_read_u32(np, "non_irq_max", &irq_max))) {
		if (irq >= irq_min && irq <= irq_max)
			found = false;
	}

	return found;
}

static void parse_node(struct drol_op *op, struct device_node *child, u32 irq, u32 val)
{
	u32 irq_cond;
	u32 source_cond;
	u32 shift;

	op->reg = recalculate_addr(child, op->reg, irq);
	if (of_property_read_bool(child, "clear_irq_shift"))
		op->clear = 1 << irq;

	if (of_property_read_bool(child, "set_irq_shift"))
		op->set = 1 << irq;
	else if (!of_property_read_u32(child, "set_source_shift", &shift))
		op->set = 1 << (val - shift);

	if (!of_property_read_u32(child, "irq_condition", &irq_cond)) {
		if (unlikely(irq_cond && irq < irq_cond))
			op->op = DROL_OP_WRITE;
	}

	if (!of_property_read_u32(child, "source_condition", &source_cond))
		op->set = val - source_cond;
}

static void drol_get_child_op(struct device_node *node, struct drol_op *op, u32 irq, u32 val)
{
	struct device_node *child = NULL;
	struct device_node *np = NULL;
	int i = 0;

	for_each_child_of_node(node, np) {
		child = of_get_next_child(np, NULL);
		if (child)
			parse_node(&op[i], child, irq, val);
		i++;
	}
}

static int drol_get_and_customize_op(struct drol_op *op, int op_size,
		const char *name, u32 hw_irq, u32 val)
{
	struct device_node *np = NULL;
	struct device_node *node = NULL;
	char *child_name = NULL;
	u32 irq;
	u32 irq_begin;
	int nr;

	if (!op || !name)
		return -EINVAL;

	node = of_find_compatible_node(NULL, NULL, name);
	if (!node)
		return -ENODEV;

	if (of_property_read_u32(node, "nton-irq-begin", &irq_begin))
		return -ENODEV;

	/* find the best sub node for nton val */
	for_each_child_of_node(node, np) {
		if (value_in_node(hw_irq, val, np))
			break;
	}
	if (!np)
		return -ENODEV;

	/* replace irq_begin with irq_min for special nton irq begin */
	(void)of_property_read_u32(np, "irq_min", &irq_begin);
	irq = hw_irq - irq_begin;

	child_name = (char *)of_get_property(np, "compatible", NULL);
	if (!child_name) {
		pr_err("[%s] %d get compatible fail.\n", __func__, __LINE__);
		return -ENODEV;
	}

	nr = drol_get_op(op, op_size, child_name, NULL);
	if (nr > 0)
		drol_get_child_op(np, op, irq, val); /* custom op by irq and val */

	return nr;
}

int drol_do_customized_op_once(const char *name, struct drol_op *op,
		int op_size, u32 irq, u32 val)
{
	int nr;
	int ret;

	nr = drol_get_and_customize_op(op, op_size, name, irq, val);
	if (nr < 0)
		return 0;

	ret = drol_map_op(op, nr, DROL_IOREMAP, DROL_IOUNMAP);
	if (ret)
		return ret;

	drol_do_op(op, nr);
	drol_unmap_op(op, nr, DROL_IOUNMAP);
	return nr;
}
EXPORT_SYMBOL(drol_do_customized_op_once);
